/**
 * Service per la gestione dei canali.
 * Attualemnte sono gestiti canali delle seguenti tipologie:
 *
 * - POLLING: 	viene eseguita una chiamata periodica verso il server.
 * 				Ogni chiamata ritorna una lista di oggetti.
 * 				Per ogni oggetto ritornato  viene generato un evento.
 * 				Il nome dell'evento è presente nell'oggetto ritornato alla proprietà ptype.
 * 				Per costruire un canale occorre utilizzare il metodo addChannel a cui visogna passare un oggetto con le seguentni proprietà:
 *				- name: nome univoco del canale
 *				- interval: intervallo in milisecondi di esecuzione
 *				- url: url da chiamare
 *				- filter: filtro dei parametri impostati da pasasre al server
 *
 * 				Ogni volta che viene invocato la chiamata ritorna la data del server che il client ritorna alla prossiam invocazione
 *
 * 				Client				Server
 * 				time=0 	 -----------> time ----->
 * 		  	 			<---------- sTime ------
 * 				time=Stime-----------> time ----->
 *
 * - MQTT:		il servizio si sottoscrive ad uno o più topic attivi su un broker e resta in attesa della pubblicazione di un nuovo
 * 				messaggio. Questa modalità di funzionamente, pur utilizzando il protocollo MQTT effettua una connessione ad una
 * 				WebSocket. Il client si occupa di anteporre il protocollo alla url del broker (ws://).
 *
 * es di canale:
 *
 * MODALITA' POLLING:
 * var ch={};
 * ch.name='prova';
 * ch.url='spireCtrl/receive';
 * ch.interval=5000;
 *
 * MODALITA' MQTT:
 * var ch={};
 * ch.name='prova';
 * ch.protocol='MQTT';
 * ch.brokerUrl='INDIRIZZO_BROKER_SENZA_PROTOCOLLO';
 * ch.brokerPort=61614; // Porta di default per le websoket in ActiveMQ
 * ch.username='[USERNAME]';
 * ch.password='[PASSWORD]';
 * ch.clientId='spimon';
 * ch.topics=['sensoristica/spire/monitoraggio'];
 * channelManager.addChannel(ch);
 * channelManager.activate('prova');
 * ATTENZIONE: La modalità MQTT funziona sfruttando le websoket come canale di comunicazione
 * (quindi se si prova a collegarsi ad un broker mptt:// il client va in errore)
 */
BootApplication.service("channelManager", ['$interval', '$rootScope', '$http', 'log', 'olFormatService', 'configuration', function($interval, $rootScope, $http, log, olFormatService, configuration) {
	var serviceName = 'Service: channelManager';

	//lista canali
	var channels = {};

	// Controllo di validità sul canale
	function valid(channel){
		return true;
	};

    function parseFeatures(chanel,response, format){
        var object=response.data;
        //leggo la data per la prossima call al server
        if(object.header && object.header.sTime){
        	//
        	chanel.lastTime = object.header.sTime;
        }

        //se c'è un payload
        if(object.data) {
        	var payloads = object.data;//lista dei dati ricevuti dal server
        	var size = object.header.size;
        	for (var i = 0; i < size; i++) {
        		var pType = payloads[i].ptype || chanel.event;

        		var geomAsWkt = payloads[i].geom != undefined ? payloads[i].geom : payloads[i].geometry;

	        	//verifico se c'è un geom
	        	if(geomAsWkt != undefined && geomAsWkt != null && geomAsWkt) {

	        		/** Ritorna la feature dalla string WKT. **/
					var feature = olFormatService.olReadFeatureToWkt(geomAsWkt, configuration.getParam("Constants").SOURCE_PROJECTION, configuration.getParam("Constants").DEST_PROJECTION);

					if(feature != null)
						payloads[i] = feature;
					else
						log.error("parseFeatures error wkt non valido")
	        	}
	        	$rootScope.$broadcast(pType, payloads[i])
        	}
        }
    };

    function readData(channel){
    	log.debug(serviceName + " - canale attivo " + channel.name);
    	//todo build filter for server
    	var filter=channel.filter||{};
    		filter.time=channel.lastTime;

    	$http({
    		 method: 'POST',
    		 url: channel.url,
    		 data: filter
    	}).then(function successCallback(response) {
    		// this callback will be called asynchronously
    		log.info(serviceName + " - canale ok... "+channel);
    		parseFeatures(channel, response)
    	}, function errorCallback(response) {
    		// called asynchronously if an error occurs
    		log.error(serviceName + " - canale in errore... "+channel.name);
    	});
    };

	function getChannelByName(channelName) {
		log.debug(serviceName + " - invocato metodo <getChannelByName> con channelName <" + channelName + ">");
		return channels[channelName];
	};

	/*************************************************************************/
	/**                FUNZIONI PER GESTIONE PROTOCOLLO MQTT                **/
	/*************************************************************************/
	function createMQTTClient(channel) {
		// Verifico se è già presente un client associato a questo canale
		if(!channel.client) {
			// se non è presente lo creo a partire dai dati presenti nel canale
			channel.clientId = channel.clientId || '';
			// TODO: l'ultima parte del clientId viene generata random!
			channel.client = new Paho.MQTT.Client(channel.brokerUrl, channel.brokerPort, "client_" + channel.clientId + "_" + parseInt(Math.random() * 100, 10));
		}
		// Verifico se il client è già connesso al broker altrimenti effettuo la connessione
		if(!channel.client.isConnected()) {
			// Opzioni di connessione
			var options = {
				// timeout del tentativo di connessione in secondi
			    timeout: 3,
				// Funzione richiamata alla ricezione del messaggio di CONNACK positivo (connessione stabilita)
			    onSuccess: function () {
			        log.info(serviceName + " - Connessione al broker <" + channel.brokerUrl + ":" + channel.brokerPort + "> avvenuta con successo");
			        // Se la connessione va a buon fine effettuo la sottoscrizione al/ai topic di interesse
			        subscribeTopic(channel);
			    },
			    // Funzione richiamata in caso di errori nella connessione
			    onFailure: function (message) {
			    	log.error(serviceName + " Connessione al broker <" + channel.brokerUrl + ":" + channel.brokerPort + "> fallita: " + message.errorMessage);
			    }
			};
			// Gestione dell'autenticazione
			if(channel.username && channel.password) {
				options.userName = channel.username;
				options.password = channel.password;
			}
			// Invia messaggio CONNECT
			channel.client.connect(options);
			// Funzione di callback richiamata all'arrivo di un nuovo messaggio da uno dei topic sottoscritti
			channel.client.onMessageArrived = function(message) {
				// il messaggio ricevuto è un oggetto che potrebbe contenere un payload di tipo ArrayBuffer
				log.info(serviceName + " Ricevuto messaggio: " + message.payloadString);
				// controllo se c'è un payload di tipo stringa
		        if(message.payloadString) {
		        	var payloads = JSON.parse(message.payloadString);//lista dei dati ricevuti dal server
		        	/**********************************************************************************************************/
		        	/** Codice recuperato dalla funzione parseFeatures per matenere la compatibilità con la modalità polling **/
	        		var pType = payloads.ptype || channel.event;

	        		var geomAsWkt = payloads.geom != undefined ? payloads.geom : payloads.geometry;

		        	//verifico se c'è un geom
		        	if(geomAsWkt != undefined && geomAsWkt != null && geomAsWkt) {

		        		/** Ritorna la feature dalla string WKT. **/
						var feature = olFormatService.olReadFeatureToWkt(geomAsWkt, configuration.getParam("Constants").SOURCE_PROJECTION, configuration.getParam("Constants").DEST_PROJECTION);

						if(feature != null)
							payloads = feature;
						else
							log.error(serviceName + " - parseFeatures error wkt non valido")
		        	}
		        	$rootScope.$broadcast(pType, payloads);
		        	/**********************************************************************************************************/
		        }
			};
		}
	};
	// Funzione utilizzata per la disattivazione del client MQTT
	function deactivateMQTTClient(channel) {
		var deactivated = false;
		// Controllo se il client è presente ed è connesso
		if(channel.client && channel.client.isConnected()) {
			subscribeTopic(channel, true);
			// Disconnetto il client dal broker
			channel.client.disconnect();
			deactivated = true;
		}
		return deactivated;
	};
	// Funzione per la sottoscrizione del client ai topics
	// @Param channel: canale del quale si deve effettuare subscribe/unsubscribe dei topics
	// @Param unsubscribe: booleano per indicare se si vogliono
	function subscribeTopic(channel, unsubscribe) {
		// Recupero i topics a cui si deve sottoscrivere il client
		var topics = channel.topics;
		$.each(topics, function(i, topic) {
			if(topic) {
				// Per ogni topic verifico se è presente anche un livello di QoS
				var tokens = topic.split(":");
				// controllo se devo effettuare una "unsubscribe" o una "subscribe"
				if(unsubscribe && unsubscribe === true) {
					channel.client.unsubscribe(tokens[0]);
				} else {
					if(tokens.length > 1) {
						// Se è presente un livello di QoS
						channel.client.subscribe(tokens[0], {qos: parseInt(tokens[1], 10)});
					} else {
						channel.client.subscribe(tokens[0]);
					}
				}
			}
		});
	}
	/*************************************************************************/
	/**                FUNZIONI PER GESTIONE PROTOCOLLO MQTT                **/
	/*************************************************************************/

	return {
		getChannel: function(chname){
		    return getChannelByName(chname);
		},
		activate: function(chname) {
			// Recupero il canale dalla lista a partire dal name
			var channel=channels[chname];
			if(channel) {
				// Se il canale è presente controllo su che protocollo di comunicazione si basa
				if(channel.protocol && channel.protocol.toUpperCase() === 'MQTT') {
					createMQTTClient(channel);
				} else {
					// Se non è stato settato alcun protocollo utilizzo la "vecchia" funzionalità (polling in base all'intervallo passato)
					var interval=channel.interval || 10000;
					channel.intervalfunc = $interval(readData, interval, 0, false, channel);
				}
			} else {
				log.error(serviceName + ' - channel '+chname+' non trovato');
			}
			return channel;
		},
		deactivate: function(chname) {
			var channel=getChannelByName(chname);
			var deleted = false;
			if(channel.protocol != undefined && channel.protocol.toUpperCase() === 'MQTT') {
				deleted = deactivateMQTTClient(channel);
			} else {
				deleted=$interval.cancel(channel.intervalfunc);
			}
			log.info(serviceName + " - task deleted:"+deleted);
		},
		addChannel: function(channel){
	    	if( valid(channel)){
	    		if($.inArray(channel.name, channels)< 0){
	    			log.info(serviceName + " - creating channel "+channel.name);
	    			channels[channel.name] = channel;
	    		}else{
	    			log.debug(serviceName + " - channel "+chanel.name+" esistente");
	    		}
	    	}else{
			   log.error(serviceName + " - channel invalid... "+channel);
			}
	    }
	}
}]);
